package com.gitee.wsl.func.observer

import com.gitee.wsl.platform.reflect.WeakReference
import kotlinx.atomicfu.locks.SynchronizedObject
import kotlinx.atomicfu.locks.synchronized


typealias OnChange<T> = (T) -> Unit
typealias Unsubscribe = () -> Unit

interface Observable<T> {
    /**
     * Subscribes to the observable for the given [observer] and the callback [block].
     */
    fun <O:Any> observe(observer: O, block: OnChange<T>): Unsubscribe
}


private class Subscription<T, O:Any>(
    val id: Int,
    val unsubscribe: WeakReference<O>,
    val subscription: OnChange<T>,
)

class Subscriptions<T> : Observable<T> {

    /** Sync lock */
    private val lock = SynchronizedObject()

    /** Id counter for subscriptions */
    private var idCounter = 0

    /** The subscriptions */
    private val subscriptions: MutableSet<Subscription<T, *>> = mutableSetOf()

    /**
     * Subscribes to the observable stream and executes the given block for each emitted item.
     */
    override fun <O:Any> observe(observer: O, block: OnChange<T>): Unsubscribe {
        val id = synchronized(lock) {
            idCounter++
        }

        val subscription = Subscription(
            id = id,
            unsubscribe = WeakReference(observer),
            subscription = block,
        )

        subscriptions.add(subscription)

        val unsubscribe: Unsubscribe = { unsubscribeId(id) }

        return unsubscribe
    }

    /**
     * Emit a new value to all observers
     */
    fun emit(value: T) {
        cleanUp()
        subscriptions.forEach { it.subscription(value) }
    }

    /**
     * Clears all subscriptions and removes them from the subscriptions list.
     */
    fun unsubscribeAll() {
        subscriptions.forEach { it.unsubscribe.clear() }
        subscriptions.clear()
    }

    /**
     * Determines whether there are any active subscriptions.
     */
    fun hasSubscriptions(): Boolean {
        cleanUp()
        return subscriptions.isNotEmpty()
    }

    /**
     * Returns the number of subscriptions
     */
    fun numSubscriptions(): Int {
        cleanUp()
        return subscriptions.size
    }

    /**
     * Removes a subscription by its id
     */
    private fun unsubscribeId(id: Int) {
        cleanUp()
        subscriptions.removeAll { it.id == id }
    }

    /**
     * Cleans up the subscriptions by removing any weak references that have been garbage collected.
     * This method removes any subscriptions from the subscriptions list where the referenced object is null.
     */
    private fun cleanUp() {
        subscriptions.removeAll { it.unsubscribe.get() == null }
    }
}

interface Observer1 {
    fun <T> Observable<T>.observe(block: OnChange<T>) = observe(this, block)
}